﻿using System;
using System.Diagnostics;
using System.Globalization;
using TomanuExtensions;
using YAXLib;

namespace RaytracerLib.MathLib
{
    /// <summary>
    /// General purpose.
    /// </summary>
    [DebuggerStepThrough]
    [DebuggerDisplay("X: {X}; Y: {Y}")]
    public struct Vector2
    {
        public static readonly Vector2 XAXIS = new Vector2(1, 0);
        public static readonly Vector2 YAXIS = new Vector2(0, 1);
        public static readonly Vector2 ZERO = new Vector2(0, 0);

        public static readonly Vector2 UNDEFINED =
            new Vector2(Double.NegativeInfinity, Double.NegativeInfinity);

        [YAXAttributeForClass]
        public readonly double X;

        [YAXAttributeForClass]
        public readonly double Y;

        public Vector2(Vector2 a_v)
        {
            X = a_v.X;
            Y = a_v.Y;
        }

        public Vector2(double a_x, double a_y)
        {
            X = a_x;
            Y = a_y;
        }

        public double Length
        {
            get
            {
                Debug.Assert(SqrLen >= 0);

                return Math.Sqrt(SqrLen);
            }
        }

        public double SqrLen
        {
            get
            {
                return X * X + Y * Y;
            }
        }

        public Vector2 Normalized
        {
            get
            {
                double length = Length;

                if (length == 0)
                    return this;

                return new Vector2(X / length, Y / length);
            }
        }

        public bool IsNormalized
        {
            get
            {
                return SqrLen.IsAlmostRelativeEquals(1);
            }
        }

        public override string ToString()
        {
            return String.Format("{0}, {1}",
                X.ToString(CultureInfo.InvariantCulture),
                Y.ToString(CultureInfo.InvariantCulture));
        }

        public static bool operator ==(Vector2 a_a, Vector2 a_b)
        {
            return (a_a.X == a_b.X) && (a_a.Y == a_b.Y);
        }

        public static bool operator !=(Vector2 a_a, Vector2 a_b)
        {
            return !(a_a == a_b);
        }

        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode();
        }

        public override bool Equals(object a_obj)
        {
            if (a_obj == null)
                return false;

            if (!typeof(Vector2).Equals(a_obj.GetType()))
                return false;
            Vector2 vector2d2 = (Vector2)a_obj;

            return (X == vector2d2.X) && (Y == vector2d2.Y);
        }

        public bool Equals(Vector2 a_vector2d2)
        {
            return (X == a_vector2d2.X) && (Y == a_vector2d2.Y);
        }

        public static Vector2 operator +(Vector2 a_a, Vector2 a_b)
        {
            return new Vector2(a_a.X + a_b.X, a_a.Y + a_b.Y);
        }

        public static Vector2 operator -(Vector2 a_a, Vector2 a_b)
        {
            return new Vector2(a_a.X - a_b.X, a_a.Y - a_b.Y);
        }

        public static Vector2 operator *(Vector2 a_a, double a_b)
        {
            return new Vector2(a_a.X * a_b, a_a.Y * a_b);
        }

        public static Vector2 operator /(Vector2 a_a, double a_b)
        {
            return new Vector2(a_a.X / a_b, a_a.Y / a_b);
        }

        public static Vector2 operator /(double a_a, Vector2 a_b)
        {
            return new Vector2(a_a / a_b.X, a_a / a_b.Y);
        }

        public static double operator *(Vector2 a_a, Vector2 a_b)
        {
            return a_a.X * a_b.X + a_a.Y * a_b.Y;
        }

        public static Vector2 operator /(Vector2 a_a, Vector2 a_b)
        {
            return new Vector2(a_a.X / a_b.X, a_a.Y / a_b.Y);
        }

        public static Vector2 operator -(Vector2 a_a)
        {
            return new Vector2(-a_a.X, -a_a.Y);
        }

        public bool IsAlmostRelativeEquals(Vector2 a_v, double a_precision = Constants.DOUBLE_PRECISION)
        {
            return X.IsAlmostRelativeEquals(a_v.X, a_precision) && Y.IsAlmostRelativeEquals(a_v.Y, a_precision);
        }

        /// <summary>
        /// Limit inclusive.
        /// </summary>
        /// <param name="a_min_x">Inclusive</param>
        /// <param name="a_max_x">Inclusive</param>
        /// <param name="a_min_y">Inclusive</param>
        /// <param name="a_max_y">Inclusive</param>
        /// <returns></returns>
        public Vector2 Limit(double a_min_x, double a_max_x, double a_min_y, double a_max_y)
        {
            return new Vector2(
                DoubleExtensions.Limit(X, a_min_x, a_max_x), 
                DoubleExtensions.Limit(Y, a_min_y, a_max_y));
        }
    }
}